using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using PaintIsland;

[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshCollider))]
[RequireComponent(typeof(Chunk))]
public class ChunkMeshGenerator : MonoBehaviour
{
    private Chunk chunk;
    private MeshFilter meshFilter;
    private MeshCollider meshCollider;
    private MeshRenderer meshRenderer;
    private Mesh mesh;

    public Mesh Mesh
    {
        get => mesh;
    }

    private void Awake()
    {
        mesh = new Mesh();
        mesh.name = "GeneratedChunkMesh";
        chunk = GetComponent<Chunk>();
        meshFilter = GetComponent<MeshFilter>();
        meshCollider = GetComponent<MeshCollider>();
        meshRenderer = GetComponent<MeshRenderer>();

        meshRenderer.material.mainTexture = TerrainLoader.GetInstance().LoadedTerrain;
    }

    public void GenerateMesh()
    {
        mesh.Clear();

        List<Vector3> vertices = new List<Vector3>();
        List<int> indices = new List<int>();
        List<Vector2> uvs = new List<Vector2>();

        var data = chunk.Data;
        

        for (var x = 0; x < Chunk.CHUNK_SIZE; x++)
        {
            for (var y = 0; y < Chunk.CHUNK_SIZE; y++)
            {
                for (var z = 0; z < Chunk.CHUNK_SIZE; z++)
                {
                    if (data[x, y, z] != BlockID.Air) 
                    {
                        Vector3Int currentPoint = new Vector3Int(x, y, z);

                        // X axis.
                        // Check & build left face
                        if ((x == 0 && (chunk.Neighbours[ChunkDirection.Left] == null || chunk.Neighbours[ChunkDirection.Left]?.Data[Chunk.CHUNK_SIZE - 1, y, z] == BlockID.Air)) || (x > 0 && chunk.Data[x - 1, y, z] == BlockID.Air))
                        {
                            BuildFace(data, BlockDef.Face.West,currentPoint, currentPoint + Vector3.forward, Vector3.up, Vector3.back, vertices, indices, uvs);
                        }
                        
                        // Check & build right face
                        if ((x == Chunk.CHUNK_SIZE - 1 && (chunk.Neighbours[ChunkDirection.Right] == null || chunk.Neighbours[ChunkDirection.Right]?.Data[0, y, z] == BlockID.Air)) || (x < Chunk.CHUNK_SIZE - 1 && chunk.Data[x + 1, y, z] == BlockID.Air))
                        {
                            BuildFace(data, BlockDef.Face.East, currentPoint, currentPoint + Vector3.right, Vector3.up, Vector3.forward, vertices, indices, uvs);
                        }
                        // Check Y axis.
                        // Build bottom face
                        if ((y == 0 && (chunk.Neighbours[ChunkDirection.Bottom] == null || chunk.Neighbours[ChunkDirection.Bottom]?.Data[x, Chunk.CHUNK_SIZE - 1, z] == BlockID.Air)) || (y > 0 && chunk.Data[x, y - 1, z] == BlockID.Air))
                        {
                            BuildFace(data, BlockDef.Face.Down, currentPoint, currentPoint + Vector3.forward, Vector3.back, Vector3.right, vertices, indices, uvs);
                        }

                        // Build top face.
                        if ((y == Chunk.CHUNK_SIZE - 1 && (chunk.Neighbours[ChunkDirection.Top] == null || chunk.Neighbours[ChunkDirection.Top]?.Data[x, 0, z] == BlockID.Air)) || (y < Chunk.CHUNK_SIZE - 1 && chunk.Data[x, y + 1, z] == BlockID.Air))
                        {
                            BuildFace(data, BlockDef.Face.Up,currentPoint, currentPoint + Vector3.up, Vector3.forward, Vector3.right, vertices, indices, uvs);
                        }

                        // Check Z axis.
                        if ((z == 0 && (chunk.Neighbours[ChunkDirection.Near] == null || chunk.Neighbours[ChunkDirection.Near]?.Data[x, y, Chunk.CHUNK_SIZE - 1] == BlockID.Air)) || (z > 0 && chunk.Data[x, y, z - 1] == BlockID.Air))
                        {
                            BuildFace(data, BlockDef.Face.South,currentPoint, currentPoint, Vector3.up, Vector3.right, vertices, indices, uvs);
                        }

                        if ((z == Chunk.CHUNK_SIZE - 1 && (chunk.Neighbours[ChunkDirection.Far] == null || chunk.Neighbours[ChunkDirection.Far]?.Data[x, y, 0] == BlockID.Air)) || (z < Chunk.CHUNK_SIZE - 1 && chunk.Data[x, y, z + 1] == BlockID.Air))
                        {
                            BuildFace(data, BlockDef.Face.North, currentPoint, currentPoint + Vector3.forward + Vector3.right, Vector3.up, Vector3.left, vertices, indices, uvs);
                        }
                    }
                }
            }
        }

        mesh.vertices = vertices.ToArray();
        mesh.triangles = indices.ToArray();
        mesh.uv = uvs.ToArray();

        mesh.RecalculateBounds();
        mesh.RecalculateNormals();

        meshFilter.mesh = mesh;
        meshCollider.sharedMesh = mesh;
    }

    public void BuildFace(
        BlockID[,,] data,
        BlockDef.Face face,
        Vector3Int position, 
        Vector3 origin,
        Vector3 up,
        Vector3 right,
        List<Vector3> vertices,
        List<int> indices,
        List<Vector2> uvs)
    {
        // int uvIndex = BlockDef.Blocks[data[position.x, position.y, position.z]];
        BlockDef def = BlockDef.Blocks[data[position.x, position.y, position.z]];
        int uvIndex = face switch
        {
            BlockDef.Face.West => def.leftTextureIndex,
            BlockDef.Face.East => def.rightTextureIndex,
            BlockDef.Face.North => def.frontTextureIndex,
            BlockDef.Face.South => def.backTextureIndex,
            BlockDef.Face.Up => def.topTextureIndex,
            BlockDef.Face.Down => def.bottomTextureIndex,
            _ => 0,
        };
         
        int curVertex = vertices.Count;

        indices.AddRange(new int[] 
        {
            curVertex + 0,
            curVertex + 1,
            curVertex + 2,
            curVertex + 2,
            curVertex + 1,
            curVertex + 3
        });

        vertices.AddRange(new Vector3[]
        {
            origin,
            origin + up,
            origin + right,
            origin + up + right
        });

        uvs?.AddRange(TerrainLoader.GetInstance().GetIndexUVs(uvIndex));
    }
}
